/*
 * AudioFactory.c
 *
 *  Created on: 09.08.2011
 *      Author: sili
 */

#include "AudioFactory.h"
#include "PropertyFactory.h"
#include "AALItem.h"
#include <vdr/device.h>

static log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("at.sili.aalsystem.factories.AudioFactory"));

static pthread_rwlock_t audioRwlock = PTHREAD_RWLOCK_INITIALIZER;
static pthread_cond_t condition_var = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t count_mutex = PTHREAD_MUTEX_INITIALIZER;

AudioFactory::AudioFactory(Controller * _controller, const char * _configDir)
	: ActionListener("AudioFactory"){

	AAL_DEBUG(logger,"Constructor");

	this->configDir.clear();
	this->configDir.append(_configDir);
	this->configDir.append("/audio");

	this->controller = _controller;
	this->controller->addListener(this);

	this->audioCacheXmlFile.clear();
	this->audioCacheXmlFile.append(_configDir);
	this->audioCacheXmlFile.append("/audioCache.xml");

	// check if an properties.xml file exists
	char * targetName = canonicalize_file_name(audioCacheXmlFile.c_str());
	if(!targetName){
		// file doesn't exists
		AAL_INFO(logger,"File: %s doesn't exists. Creating a new one.", audioCacheXmlFile.c_str());
		serializeCacheFile();
	}
	delete(targetName);

	// check if the audio directory exists
	targetName = canonicalize_file_name(this->configDir.c_str());
	if(!targetName){
		// directory doesn't exists
		AAL_INFO(logger, "Directory %s doesn't exists. Creating a new one", this->configDir.c_str());
		mkdir(this->configDir.c_str(), 0777);
	}
	delete(targetName);

	deSerializeCacheFile();

	//this->audioActions.clear();

	this->mrl.clear();
	mrl.append("file:/");
	mrl.append(_configDir);
	//mrl.append("/audio/output.wav");
	mrl.append("/audio/alert.mp3");


	xine_ = ::xine_new();
	xine_init(xine_);
	int major, minor, sub;
	major = minor = sub = -1;
	xine_get_version(&major, &minor,&sub);
	//AAL_TRACE(logger,"[xinemedia] libxine version %d.%d.%d",  major, minor, sub);
	//AAL_TRACE(logger,"[xinemedia] vdr plugin built on %s", XINE_VERSION);
	//AAL_TRACE(logger,"[xinemedia] versions %s compatible",   xine_check_version(major, minor, sub)==1 ? "are" : "are NOT!");
	xine_engine_set_param(xine_, XINE_ENGINE_PARAM_VERBOSITY,  XINE_VERBOSITY_LOG);
	xine_engine_set_param(xine_, XINE_PARAM_VERBOSITY,  XINE_VERBOSITY_LOG);

	int volume = this->controller->getPropertyFactory()->getIntProperty(AALPROP_AUDIO_VOLUME);
	AAL_DEBUG(logger, "Setting volume to %d", volume);
	xine_engine_set_param(xine_, XINE_PARAM_AUDIO_VOLUME, volume);

	// SetXineConfig();
	//aoPort_ = ::xine_open_audio_driver(xine_, "hde-audio", 0);
	aoPort_ = ::xine_open_audio_driver(xine_, "alsa", 0);
	// voPort_ = ::xine_open_video_driver(xine_, "hde-video-aa", XINE_VISUAL_TYPE_AA, nullptr);
	stream_ = xine_stream_new(xine_, aoPort_, NULL);
	// char *mrl="file://media/hd/music/ring.mp3";
	// char *mrl="file://media/hd/music/alert.wav";
	Start();
}

AudioFactory::~AudioFactory() {
	AAL_TRACE(logger,"Destructor");
	xine_dispose(stream_);
	xine_close_audio_driver(xine_, aoPort_);
	//xine_close_video_driver(xine_, vp);
	xine_exit(xine_);

	if(this->maryClient)
		delete(this->maryClient);

	serializeCacheFile();
}

void AudioFactory::Action(void){

	while(Running()){
		// Lock mutex and then wait for signal to relase mutex
		pthread_mutex_lock( &count_mutex );
		pthread_cond_wait(&condition_var, &count_mutex );

		if(this->audioActions.size() > 0){
			AALAction * audioAction = this->audioActions.back();
			this->audioActions.pop_back();

			// adjust volume ALSA
			int volume = this->controller->getPropertyFactory()->getIntProperty(AALPROP_AUDIO_VOLUME);
			xine_set_param(stream_, XINE_PARAM_AUDIO_VOLUME, volume);

			// TODO TURN OFF VOLUME REEL
			cDevice * cdev = cDevice::ActualDevice();
			if(!cdev->IsMute()){
				AAL_TRACE(logger, "Cdev isn't muted");
				// cdev->Mute();
			} else {
				AAL_TRACE(logger, "Cdev is muted");
			}

			int currentVolume = cdev->CurrentVolume();
			AAL_DEBUG(logger, "Current device volume is %d", currentVolume);
			//cdev->SetVolume(volume,true);

			this->mrl.clear();
			this->mrl.append(this->configDir);
			this->mrl.append("/alert.mp3");

			char randFile[500];

			// chec audio otupt type (e.g. beep, espeak or mary)
			const char * audioType = this->controller->getPropertyFactory()->getStringProperty(AALPROP_AUDIO_TYPE);


			if(audioType != NULL){
				char audioFileName[1000];
				snprintf(audioFileName, (size_t)999, "%s_%s", audioType, audioAction->message.c_str());
				const char * audioCacheFilename = this->getAudioCacheFilename(audioFileName);
				if(audioCacheFilename != NULL){
					AAL_DEBUG(logger, "Using audioCacheFilename %s", audioCacheFilename);
					strcpy(randFile, audioCacheFilename);
				} else {
					snprintf(randFile, (size_t)499, "%s/%d.wav", this->configDir.c_str(), rand());
					if(strcmp(AALPROP_AUDIO_TYPE_BEEP,audioType) == 0){
						snprintf(randFile, (size_t)499, "%s/alert.mp3", this->configDir.c_str());
					} else if(strcmp(AALPROP_AUDIO_TYPE_ESPEAK,audioType) == 0){

						char cmd[500];
						snprintf(cmd, (size_t)499, "espeak -vde -w %s \"%s\"", randFile, audioAction->message.c_str());
						AAL_DEBUG(logger, "Executing cmd: %s", cmd);

						string cmdResult = "";
						Tools::execCmd(cmd, &cmdResult);
						AAL_DEBUG(logger, "Cmd returned: %s", cmdResult.c_str());
						this->saveCacheFile(audioFileName, randFile);

					} else if(strcmp(AALPROP_AUDIO_TYPE_MARY,audioType) == 0){

						if(this->maryClient == NULL)
							this->maryClient = new MaryClient;


						const char * server_host = this->controller->getPropertyFactory()->getStringProperty(AALPROP_TTS_MARY_HOST);
						int server_port = this->controller->getPropertyFactory()->getIntProperty(AALPROP_TTS_MARY_PORT);

						string maryInFormat = "TEXT";
						string maryOutFormat = "AUDIO";
						string locale = "de";
						string audioType = "WAV_FILE";
						string voice = "dfki-pavoque-neutra";
						string result;
						string effects;

						this->maryClient->maryQuery( server_port, server_host, result, audioAction->message.c_str(), maryInFormat, maryOutFormat, locale, audioType, voice, effects);

						ofstream file(randFile);
						file << result;
						file.close();
						this->saveCacheFile(audioFileName, randFile);
					} else {
						AAL_DEBUG(logger, "Can't use any audio cause given audio type %s doesn't match any internal type", audioType);
						audioType = NULL;
					}
					AAL_DEBUG(logger, "Can't use audioCacheFilename because it doesn't exists. -> Create new cache file %s", randFile);
				}
			}

			if(audioType){
				audioAction->data.clear();
				audioAction->data.append(randFile);

				this->mrl.clear();
				this->mrl.append(randFile);

				int openRes = xine_open(stream_,this->mrl.c_str());
				AAL_DEBUG(logger, "Starting audio %s %d", mrl.c_str(),openRes);
				xine_play(stream_,0,0);

				int posStream = -1;
				int pos = -1;
				int length = -1;

				xine_get_pos_length (stream_, &posStream, &pos, &length);
				AAL_TRACE(logger, "Playing posStream %d pos %d length %d", posStream, pos, length);
			}
		}
		pthread_mutex_unlock( &count_mutex );
	}
}

const char * AudioFactory::getAudioCacheFilename(const char * _text){
	if(_text == NULL){
		AAL_WARN(logger, "Can't return audiCacheFilenem for an empty _text");
		return NULL;
	}
	string key(_text);

	map<string,string>::iterator it = this->audioCache.find(key);
	if(it != this->audioCache.end()){
		string value = it->second;
		return value.c_str();
	}

	return NULL;
}

void AudioFactory::doAction(AALAction * _action){
	if(_action->type == doPlayAudio){
		AAL_INFO(logger, "%s id:%ld type: %s message:\"%s\" aalItemId:%d", ((ActionListener*)this)->getName(),_action->id, _action->typeToString(), _action->message.c_str(), _action->aalItem != NULL ? _action->aalItem->getId():-1);
		pthread_rwlock_rdlock(&audioRwlock);
		pthread_mutex_lock( &count_mutex );
		this->audioActions.push_back(_action);
		xine_close(stream_);
		pthread_cond_signal( &condition_var );
		pthread_mutex_unlock( &count_mutex );
		pthread_rwlock_unlock(&audioRwlock);
	}
}

void AudioFactory::doMainSavedAction(){
	// nothing to do here.
	// Audio playback isn't triggered by the continuous 1sec vdr loop.
	// It is triggered by the Visualizer
}

void AudioFactory::discardAction(AALAction * _action){
	pthread_mutex_lock( &count_mutex );
	if(_action->type == doPlayAudio){
		// search this _action in the local queue
		vector<AALAction *>::iterator entry;
		for (entry=this->audioActions.begin(); entry<this->audioActions.end(); entry++ ){
			if(_action==(*entry)){
				this->audioActions.erase(entry);
				// delete(*childrenIt); <---- don't do delete. We got here cause an action destructor called this method !!!

				if(this->mrl.find(_action->data) != string::npos){
					xine_close(stream_);
				}

				cDevice * cdev = cDevice::ActualDevice();
				if(cdev->IsMute()){
					AAL_TRACE(logger, "Cdev is muted");
					//cdev->ToggleMute();
				} else {
					AAL_TRACE(logger, "Cdev isn't muted");
				}
				break;
			}
		}
	}
	pthread_mutex_unlock( &count_mutex );
}

void AudioFactory::serializeCacheFile(){

	pthread_rwlock_rdlock(&audioRwlock);

	XMLCh tempStr0[100];
	XMLCh tempStr1[100];

	XMLString::transcode("LS", tempStr0, 99);
	DOMImplementation * impl = DOMImplementationRegistry::getDOMImplementation(tempStr0);
	XMLString::transcode("AudioCache", tempStr0, 99);
	DOMDocument * doc = impl->createDocument(0, tempStr0, 0);

	map<string, string>::iterator itemValuePropIt;
	for (itemValuePropIt=this->audioCache.begin(); itemValuePropIt!=this->audioCache.end(); itemValuePropIt++ ){

		string key = itemValuePropIt->first;
		string value = itemValuePropIt->second;

		AAL_DEBUG(logger, "SerializeCacheFile, creating entry key:%s value:%s", key.c_str(), value.c_str());

		XMLString::transcode("CacheEntry", tempStr0, 99);
		DOMElement * item = doc->createElement(tempStr0);

		XMLString::transcode("key", tempStr0, 99);
		XMLString::transcode(key.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		XMLString::transcode("value", tempStr0, 99);
		XMLString::transcode(value.c_str(), tempStr1, 99);
		item->setAttribute(tempStr0,tempStr1);

		doc->getDocumentElement()->appendChild(item);
	}

	Tools::createXmlFileFromDOMDocument(this->audioCacheXmlFile.c_str(),doc);
	doc->release();
	AAL_DEBUG(logger, "SerializeCacheFile, file: %s created.", this->audioCacheXmlFile.c_str());

	pthread_rwlock_unlock(&audioRwlock);

}

void AudioFactory::deSerializeCacheFile(){

	pthread_rwlock_rdlock(&audioRwlock);
	AAL_DEBUG(logger, "DeSerializeCacheFile, file: %s", this->audioCacheXmlFile.c_str());
	XercesDOMParser parser;
	DOMDocument * doc = Tools::createDOMFromXmlFile(this->audioCacheXmlFile.c_str(), &parser);
	if(doc != NULL){
		DOMNodeList * childNodes = doc->getDocumentElement()->getChildNodes();
		for(XMLSize_t childIndex = 0; childIndex < childNodes->getLength(); ++childIndex ){
			DOMNode * childNode = childNodes->item(childIndex);

			char * value = XMLString::transcode(childNode->getNodeName());
			if(strcmp(value,"CacheEntry") != 0){
				continue;
			}
			XMLString::release(&value);

			DOMElement * child = (DOMElement*)childNodes->item(childIndex);

			string propKey = "";
			Tools::getStringAttributeValue(child, "key", &propKey);
			string propValue = "";
			Tools::getStringAttributeValue(child, "value", &propValue);

			this->audioCache.insert(pair<string,string>(propKey,propValue));
		}
	} else {
		AAL_WARN(logger, "DeSerializeCacheFile, can't deserialize file: %s", this->audioCacheXmlFile.c_str());
	}
	pthread_rwlock_unlock(&audioRwlock);
}

void AudioFactory::saveCacheFile(const char * _key, const char * _value){
	AAL_DEBUG(logger, "SaveCacheFile, key:%s value:%s", _key, _value);
	string key(_key);
	string value(_value);
	this->audioCache[key] = value;
	serializeCacheFile();
	deSerializeCacheFile();
}

void AudioFactory::deleteCacheFile(const char * _key){
	string key(_key);
	if(this->audioCache.erase(key) == 1){
		serializeCacheFile();
		deSerializeCacheFile();
		if( remove(_key) != 0 ){
			AAL_ERROR(logger, "DeleteCacheFile, can't delete key:%s", _key);
		} else {
			AAL_DEBUG(logger, "DeleteCacheFile, key:%s deleted", _key);
		}
	} else {
		AAL_ERROR(logger, "DeleteCacheFile, can't delete key:%s", _key);
	}
}
